<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */

namespace App\Logic;

use App\Model\FollowAnchor;
use App\Model\Sign;
use App\Model\SmsLog;
use App\Model\User;
use App\Model\UserCoin;
use App\Model\UserCoupon;
use App\Model\UserIntegral;
use App\Model\UserLog;
use App\Model\UserTask;
use App\Model\UserToken;
use App\Service\SettingService;
use App\Utils\Enum\AccountEnum;
use App\Utils\Enum\RedisEnum;
use App\Utils\Enum\SceneEnum;
use App\Utils\Enum\UserEnum;
use App\Utils\Log;
use Exception;
use Hyperf\Context\ApplicationContext;
use Hyperf\Database\Model\Model;
use Hyperf\DbConnection\Db;
use Hyperf\Redis\Redis;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use RedisException;
use Throwable;

class UserLogic
{
    /**
     * 注册
     * @param $input
     * @param $client
     * @return Model|User
     * @throws Exception
     */
    public static function register($input, $client = 0): Model|User
    {
        if (empty($input['mobile']) && empty($input['email']) && $input['roleId'] != UserEnum::USER_TYPE_VISITOR) {
            throw new Exception('注册方式不支持');
        }
        $salt = create_randomstr();
        try {
            return User::create([
                'im_id' => ImLogic::createImID(),
                'username' => $input['username'],
                'nickname' => $input['nickname'] ?? $input['username'],
                'mobile' => $input['mobile'] ?? '',
                'email' => $input['email'] ?? '',
                'country_code' => $input['countryCode'] ?? '+86',
                'avatar' => $input['avatar'] ?? '',
                'role_id' => $input['roleId'] ?? UserEnum::USER_TYPE_NORMAL,
                'password' => create_password($input['password'], $salt),
                'salt' => $salt,
                'balance' => 0,
                'integral' => 0,
                'is_virtual' => $input['isVirtual'] ?? 0,
                'device_id' => $input['deviceId'] ?? '',
                'reg_time' => time(),
                'reg_ip' => $input['ip'] ?? getClientIp(),
                'reg_client' => $client,
            ]);
        } catch (Exception $e) {
            Log::warning(sprintf('帐号注册失败 error=%s', $e->getMessage()));
            throw new Exception('注册失败，请稍后再试');
        }
    }

    /**
     * 账号或邮箱登录.
     * @throws Exception
     */
    public static function accountLogin(array $input): array
    {
        if (filter_var($input['account'], FILTER_VALIDATE_EMAIL)) {
            $user = User::query()->where('email', $input['account'])->first();
        } else {
            $user = User::query()->where('username', $input['account'])->first();
        }
        if (!$user || $user['password'] != create_password($input['password'], $user['salt'])) {
            throw new Exception('账号或密码错误');
        }

        return self::loginSuccess($user, $input['client'], 'account');
    }

    /**
     * @param mixed $input
     * @throws Exception
     */
    public static function mobileLogin($input): array
    {
        if (empty($input['password']) && !empty($input['code'])) {
            return self::otpLogin($input);
        }
        $user = User::query()->where('mobile', $input['mobile'])->first();
        if (!$user || $user['password'] != create_password($input['password'], $user['salt'])) {
            throw new Exception('手机号码或密码错误');
        }
        return self::loginSuccess($user, $input['client'], 'mobile');
    }

    /**
     * 手机验证码登录.
     * @param mixed $input
     * @throws Exception
     */
    public static function otpLogin($input): array
    {
        $user = User::query()->where('mobile', $input['mobile'])->first();
        if (!$user || !$validId = validate_sms_code(SceneEnum::LOGIN, $input['mobile'], $input['code'])) {
            throw new Exception('手机号码或验证码错误');
        }

        return self::loginSuccess($user, $input['client'], 'otp', $validId);
    }

    /**
     * 修改密码
     * @param $userid
     * @param $oldPassword
     * @param $newPassword
     * @return true
     * @throws Exception
     */
    public static function changePassword($userid, $oldPassword, $newPassword): true
    {
        $user = User::find($userid);
        if (empty($user)) {
            throw new Exception('账号不存在');
        }
        if (create_password($oldPassword, $user['salt'], strlen($oldPassword) == 32) != $user['password']) {
            throw new Exception('旧密码不正确');
        }
        $user->password = create_password($newPassword, $user['salt'], strlen($newPassword) == 32);
        if (!$user->save()) {
            throw new Exception('修改失败');
        }
        //清TOKEN
        UserToken::where('userid', $userid)->delete();
        //TODO 推送自动下线事件
        return true;
    }

    /**
     * 重置密码
     * @param mixed $userid
     * @param mixed $password
     * @return true
     * @throws Exception
     */
    public static function resetPassword($userid, $password): true
    {
        $salt = create_randomstr(6);
        $password = create_password($password, $salt);
        $ret = User::query()->where('id', $userid)->update(['salt' => $salt, 'password' => $password]);
        if (!$ret) {
            throw new Exception('重置失败');
        }

        // 失效token
        $tokens = UserToken::query()->where('userid', $userid)->select('token')->get()->pluck('token')->toArray();
        if (!empty($tokens)) {
            try {
                $redis = ApplicationContext::getContainer()->get(Redis::class);
                array_walk($tokens, function (&$v) {
                    $v = RedisEnum::PREFIX_TOKEN . $v;
                });
                $redis->del($tokens);
                UserToken::query()->where('userid', $userid)->delete();
                //TODO 下发退出账号命令
            } catch (ContainerExceptionInterface|NotFoundExceptionInterface|RedisException $e) {
                // TODO 日志
            }
        }
        UserToken::where('userid', $userid)->delete();

        self::createLog($userid, 'resetPassword');
        return true;
    }

    public static function createLog($userid, $logType, $logData = []): void
    {
        UserLog::create([
            'userid' => $userid,
            'log_type' => $logType,
            'log_data' => new_json_encode($logData),
            'ip' => getClientIp(),
            'add_time' => time(),
        ]);
    }

    public static function createToken($userinfo, $client): false|string
    {
        try {
            $redis = ApplicationContext::getContainer()->get(Redis::class);
            $oldToken = UserToken::query()->where([['userid', '=', $userinfo['id']], ['client', '=', $client]])->first();
            $token = create_randomstr(32);
            if ($oldToken) {
                $redis->del(RedisEnum::PREFIX_TOKEN . $oldToken['token']);
                UserToken::query()->where('id', $oldToken['id'])->update([
                    'token' => $token,
                    'create_time' => time(),
                    'expire_time' => time() + 30 * 3600,
                    'update_time' => time(),
                ]);
            } else {
                UserToken::create([
                    'userid' => $userinfo['id'],
                    'client' => $client,
                    'token' => $token,
                    'create_time' => time(),
                    'expire_time' => time() + 30 * 3600,
                    'update_time' => time(),
                ]);
            }
            $redis->hMSet(RedisEnum::PREFIX_TOKEN . $token, [
                'userid' => $userinfo['id'],
                'username' => $userinfo['username'],
                'nickname' => $userinfo['nickname'],
                'mobile' => $userinfo['mobile'],
                'email' => $userinfo['email'],
                'roleId' => $userinfo['role_id'],
            ]);
            $redis->expire(RedisEnum::PREFIX_TOKEN . $token, 30 * 3600);
            return $token;
        } catch (Throwable $e) {
            echo $e->getMessage();
            // TODO
            return false;
        }
    }

    public static function updateTokenInfo($userid): true
    {
        $userinfo = User::find($userid);
        if ($userinfo) {
            $tokens = UserToken::query()->where('userid', $userid)->select('token')->get();
            try {
                $redis = ApplicationContext::getContainer()->get(Redis::class);
                foreach ($tokens as $token) {
                    if ($redis->exists($token['token'])) {
                        $redis->hMSet(RedisEnum::PREFIX_TOKEN . $token['token'], [
                            'userid' => $userinfo['userid'],
                            'username' => $userinfo['username'],
                            'nickname' => $userinfo['nickname'],
                            'mobile' => $userinfo['mobile'],
                            'email' => $userinfo['email'],
                        ]);
                    }
                }
            } catch (ContainerExceptionInterface|NotFoundExceptionInterface|RedisException $e) {
            }
        }
        return true;
    }

    /**
     * 获取用户任务情况
     * @param $userid
     * @return array
     */
    public static function getUserTask($userid){
        $task = UserTask::query()->where(['userid' => $userid, 'date' => date('Ymd')])->first();
        if (!$task) {
            return UserTask::create([
                'userid' => $userid,
                'date' => date('Ymd'),
                'chat_times' => 0,
                'buy_project_times' => 0,
                'online_minutes' => 0,
                'last_online_time' => 0,
                'view_article_times' => 0,
                'watch_live_minutes' => 0,
                'share_house_times' => 0
            ])->toArray();
        }
        return $task->toArray();
    }

    /**
     * 任务
     * @param $userid
     * @param $taskType
     * @return false|array
     */
    public static function doTask($userid, $taskType): false|array
    {
        $task = self::getUserTask($userid);
        $setting = SettingService::getGroupSetting('task');
        if ($taskType == UserEnum::TASK_READ_ARTICLE) {
            [$readReward, $maxRewardTimes] = explode('|', $setting['task_read_article_reward']);
            if($task['view_article_times'] < $maxRewardTimes) {
                UserTask::query()->where('id', $task['id'])->increment('view_article_times');
                UserLogic::incrIntegral($userid, $readReward, AccountEnum::TYPE_TASK_REWARD, '阅读任务奖励');
                return ['rewardType' => 'integral', 'rewardAmount' => $readReward];
            }
        } elseif ($taskType == UserEnum::TASK_BUY_PROJECT) {
            [$projectReward, $maxRewardTimes] = explode('|', $setting['task_buy_project_reward']);
            if ($task['buy_project_times'] < $maxRewardTimes) {
                UserTask::query()->where('id', $task['id'])->increment('buy_project_times');
                UserLogic::incrIntegral($userid, $projectReward, AccountEnum::TYPE_TASK_REWARD, '购买爆料任务奖励');
                return ['rewardType' => 'integral', 'rewardAmount' => $projectReward];
            }
        } elseif ($taskType == UserEnum::TASK_SHARE_LIVE) {
            [$shareReward, $maxRewardTimes] = explode('|', $setting['task_live_house_share_reward']);
            if ($task['share_house_times'] < $maxRewardTimes) {
                UserTask::query()->where('id', $task['id'])->increment('share_house_times');
                UserLogic::incrCoins($userid, $shareReward, AccountEnum::TYPE_TASK_REWARD, '分享直播间任务奖励');
                return ['rewardType' => 'coins', 'rewardAmount' => $shareReward];
            }
        } elseif ($taskType == UserEnum::TASK_ONLINE_TIME && time() - $task['last_online_time'] >= 60) {
            $task['online_minutes']++;
            UserTask::query()->where('id', $task['id'])->update(['online_minutes' => Db::raw('`online_minutes` + 1'), 'last_online_time' => time()]);
            $onlineRewards = explode('|', $setting['task_online_reward']);
            foreach ($onlineRewards as $_onlineSet) {
                [$minMinutes, $rewardAmount] = explode('~', $_onlineSet);
                if($task['online_minutes'] == $minMinutes) {
                    UserLogic::incrIntegral($userid, $rewardAmount, AccountEnum::TYPE_TASK_REWARD, "在线{$minMinutes}分钟任务奖励");
                    return ['rewardType' => 'integral', 'rewardAmount' => $rewardAmount, 'onlineMinutes' => $minMinutes];
                }
            }
        }
        return false;
    }

    /**
     * 获取用户任务情况.
     * @param mixed $userid
     * @return array|array[]
     */
    public static function getUserTaskInfo($userid): array
    {
        $task = self::getUserTask($userid);
        $setting = SettingService::getGroupSetting('task');

        $list = ['integral' => [], 'coins' => []];
        // 聊天发言
        [$chatReward, $chatRewardMax] = explode('|', $setting['task_chat_reward']);
        $list['integral'][] = [
            'name' => 'chat',
            'icon' => 'chat',
            'desc' => '聊天发言',
            'reward' => (int)$chatReward,
            'count' => min((int)$task['chat_times'], $chatRewardMax),
            'limit' => (int)$chatRewardMax,
            'guide' => 'home',
        ];

        // 爆料购买
        [$projectReward, $projectRewardMax] = explode('|', $setting['task_buy_project_reward']);
        $list['integral'][] = [
            'name' => 'buy_project',
            'icon' => 'project',
            'desc' => '购买爆料',
            'reward' => (int)$projectReward,
            'count' => min((int)$task['buy_project_times'], $projectRewardMax),
            'limit' => (int)$projectRewardMax,
            'guide' => 'redbill',
        ];

        // 在线时长
        $onlineRewards = explode('|', $setting['task_online_reward']);
        foreach ($onlineRewards as $_onlineSet) {
            [$minMinutes, $rewardAmount] = explode('~', $_onlineSet);
            $list['integral'][] = [
                'name' => 'online_' . $minMinutes . 'm',
                'icon' => 'online',
                'desc' => '在线时长' . $minMinutes . '分钟',
                'reward' => (int)$rewardAmount,
                'count' => $task['online_minutes'] >= $minMinutes ? 1 : 0,
                'limit' => 1,
                'guide' => 'home',
            ];
        }

        // 资讯阅读
        [$readReward, $readRewardMax] = explode('|', $setting['task_read_article_reward']);
        $list['integral'][] = [
            'name' => 'read_article',
            'icon' => 'article',
            'desc' => '资讯阅读',
            'reward' => (int)$readReward,
            'count' => intval(min($task['view_article_times'], $readRewardMax)),
            'limit' => (int)$readRewardMax,
            'guide' => 'article',
        ];

        // 观看直播
        [$watchReward, $onceMinutes, $dayMaxTimes] = explode('|', $setting['task_watch_live_reward']);
        $list['coins'][] = [
            'name' => 'watch_live',
            'icon' => 'live',
            'desc' => '观看直播',
            'reward' => (int)$watchReward,
            'count' => min(intval($task['watch_live_minutes'] / $onceMinutes), $dayMaxTimes),
            'limit' => (int)$dayMaxTimes,
            'guide' => 'home',
        ];

        // 分享直播间
        [$shareReward, $dayMaxTimes] = explode('|', $setting['task_live_house_share_reward']);
        $list['coins'][] = [
            'name' => 'share_live_house',
            'icon' => 'share',
            'desc' => '分享直播间',
            'reward' => (int)$shareReward,
            'count' => min($task['share_house_times'], $dayMaxTimes),
            'limit' => (int)$dayMaxTimes,
            'guide' => 'article',
        ];
        return $list;
    }

    /**
     * 获取签到记录.
     * @param mixed $userid
     */
    public static function getSignRecord($userid): array
    {
        $continueSignDays = 0;
        $todaySigned = false;
        $lastSign = Sign::query()->where('userid', $userid)->orderBy('id', 'desc')->first();
        if ($lastSign) {
            $continueSignDays = $lastSign['days'];
            if (date('Ymd', $lastSign['add_time']) == date('Ymd')) {
                $todaySigned = true;
            }
        }
        $setting = SettingService::getGroupSetting('sign');
        $list = [];
        $day = 1;
        foreach (explode("\n", $setting['sign_reward']) as $line) {
            if (!str_contains($line, '|')) {
                continue;
            }
            [$integral, $coins] = explode('|', trim($line));
            $list[] = [
                'day' => $day,
                'integral' => (int)$integral,
                'coins' => (int)$coins,
                'completed' => $continueSignDays >= $day,
            ];
            ++$day;
        }
        return [
            'rewards' => $list,
            'continueDays' => $continueSignDays,
            'todaySigned' => $todaySigned,
        ];
    }

    /**
     * 签到.
     * @param mixed $userid
     * @throws Exception
     */
    public static function sign($userid): array
    {
        $setting = SettingService::getGroupSetting('sign');
        if (!$setting['sign_enable']) {
            throw new Exception('签到功能暂时关闭中');
        }
        $lastSign = Sign::query()->where('userid', $userid)->orderBy('id', 'DESC')->first();
        if ($lastSign && date('Ymd') == $lastSign['date']) {
            throw new Exception('今天已经签到过了');
        }

        $setting['sign_reward'] = explode("\n", trim($setting['sign_reward']));

        $continueDays = 1;
        if ($lastSign && $lastSign['date'] == date('Ymd', strtotime('-1 day'))) {
            $continueDays = $lastSign['days'] + 1;
        }
        if ($continueDays > count($setting['sign_reward'])) {
            $rewardStr = end($setting['sign_reward']);
        } else {
            $rewardStr = $setting['sign_reward'][0];
        }
        $coins = 0;
        $integral = 0;
        if ($rewardStr) {
            [$coins, $integral] = explode('|', trim($rewardStr));
        }

        $sign = Sign::create([
            'userid' => $userid,
            'coins' => (int)$coins,
            'integral' => (int)$integral,
            'days' => $continueDays,
            'date' => date('Ymd'),
            'add_time' => time(),
        ]);
        if ($sign->id) {
            if ($coins) {
                self::incrCoins($userid, $coins, AccountEnum::TYPE_SIGN_REWARD, "连续第{$continueDays}天签到");
            }
            if ($integral) {
                self::incrIntegral($userid, $coins, AccountEnum::TYPE_SIGN_REWARD, "连续第{$continueDays}天签到");
            }
            return [
                'signDays' => $sign['days'],
                'coins' => $sign['coins'],
                'integral' => $sign['integral'],
            ];
        }
        throw new Exception('签到失败');
    }

    /**
     * 金币收入.
     * @param mixed $userid
     * @param mixed $coins
     * @param mixed $typeid
     * @param mixed $remark
     */
    public static function incrCoins($userid, $coins, $typeid, $remark = '', array $extend = []): bool
    {
        return self::_opAccount($userid, AccountEnum::DIRECTION_IN, AccountEnum::COINS, $coins, $typeid, $remark, $extend);
    }

    /**
     * 金币消费.
     * @param mixed $remark
     * @param mixed $userid
     * @param mixed $coins
     * @param mixed $typeid
     */
    public static function decrCoins($userid, $coins, $typeid, $remark = '', array $extend = []): bool
    {
        return self::_opAccount($userid, AccountEnum::DIRECTION_OUT, AccountEnum::COINS, $coins, $typeid, $remark, $extend);
    }

    /**
     * 积分收入.
     * @param mixed $userid
     * @param mixed $coins
     * @param mixed $typeid
     * @param mixed $extend
     * @param mixed $remark
     */
    public static function incrIntegral($userid, $coins, $typeid, $remark = '', $extend = []): bool
    {
        return self::_opAccount($userid, AccountEnum::DIRECTION_IN, AccountEnum::INTEGRAL, $coins, $typeid, $remark, $extend);
    }

    /**
     * 积分消费.
     * @param mixed $userid
     * @param mixed $coins
     * @param mixed $typeid
     * @param mixed $extend
     * @param mixed $remark
     */
    public static function decrIntegral($userid, $coins, $typeid, $remark = '', $extend = []): bool
    {
        return self::_opAccount($userid, AccountEnum::DIRECTION_OUT, AccountEnum::INTEGRAL, $coins, $typeid, $remark, $extend);
    }

    /**
     * 获取配置.
     */
    public static function getSetting(): array
    {
        $setting = SettingService::getGroupSetting('user');

        $data = [];

        foreach (explode("\n", $setting['user_level']) as $line) {
            if (!str_contains($line, '|')) {
                continue;
            }
            [$level, $levelName, $minIntegral] = explode('|', trim($line));
            $data['user_level'][$level] = [
                'level' => (int)$level,
                'levelName' => $levelName,
                'min_integral' => (int)$minIntegral,
            ];
        }
        foreach ($setting as $name => $value) {
            if (!isset($data[$name])) {
                $data[$name] = $value;
            }
        }
        return $data;
    }

    /**
     * 获取等级信息.
     * @param mixed $integral
     */
    public static function getLevelInfo($integral): array
    {
        $setting = self::getSetting();
        $data = [
            'curLevel' => 0,
            'curLevelName' => '',
            'nextLevelName' => '',
            'nextLevelIntegralLess' => 0,
        ];
        foreach ($setting['user_level'] as $set) {
            if ($integral >= $set['min_integral']) {
                $data['curLevel'] = $set['level'];
                $data['curLevelName'] = $set['levelName'];
            }
        }
        if (isset($setting['user_level'][$data['curLevel'] + 1])) {
            $data['nextLevelName'] = $setting['user_level'][$data['curLevel'] + 1]['levelName'];
            $data['nextLevelIntegral'] = $setting['user_level'][$data['curLevel'] + 1]['min_integral'];
            $data['nextLevelIntegralLess'] = $setting['user_level'][$data['curLevel'] + 1]['min_integral'] - $integral;
        }
        return $data;
    }

    /**
     * 获取用户等级信息
     * @param $integral
     * @return array
     */
    public static function getUserLevelInfo($integral): array
    {
        static $levelSetting;
        if (is_null($levelSetting)) {
            $levelSetting = self::getSetting();
        }
        $curLevelNum = 1;
        $curLevelName = '';
        $curlLevelIdx = 0;
        $upgradeIntegral = 0;
        foreach ($levelSetting['user_level'] as $i => $set) {
            if ($integral >= $set['min_integral']) {
                $curLevelNum = $set['level'];
                $curLevelName = $set['levelName'];
                $curlLevelIdx = $i;
            }
        }
        $curlLevelIdx++;
        if(isset($levelSetting['user_level'][$curlLevelIdx])) {
            $upgradeIntegral = $levelSetting['user_level'][$curlLevelIdx]['min_integral'] - $integral;
        }

        return [
            'levelNum' => $curLevelNum,
            'levelName' => $curLevelName,
            'upgradeIntegral' => $upgradeIntegral
        ];
    }

    /**
     * 获取用户金币记录.
     * @param mixed $userid
     * @param mixed $input
     */
    public static function getCoinRecords($userid, $input): array
    {
        [, $pageSize, $offset] = parsePageParams($input);

        $query = UserCoin::query()->where('userid', $userid);

        //类别
        if (isset($input['query']['direction']) && intval($input['query']['direction'])) {
            $query->where('direction', (int)$input['query']['direction']);
        }
        //月份
        if (!empty($input['query']['date'])) {
            $query->where('idx_month', intval($input['query']['date']));
        }
        $total = $query->count();
        if ($total) {
            $records = $query->offset($offset)->limit($pageSize)->select(['direction', 'typeid', 'amount', 'balance', 'remark', 'add_time'])->orderBy('id', 'desc')->get()->toArray();
            array_walk($records, function (&$v) {
                $v['directionName'] = AccountEnum::getDirection($v['direction']);
                $v['typeName'] = AccountEnum::getTypeName($v['typeid']);
                $v['createTime'] = date('Y-m-d H:i:s', $v['add_time']);
                unset($v['add_time']);
            });
        }
        return [
            'total' => $total,
            'pages' => ceil($total / $pageSize),
            'records' => $records ?? [],
        ];
    }

    /**
     * 获取积分记录.
     * @param mixed $userid
     * @param mixed $input
     */
    public static function getIntegralRecords($userid, $input): array
    {
        [,$pageSize, $offset] = parsePageParams($input);

        $total = UserIntegral::query()->where('userid', $userid)->when(isset($input['date']) && intval($input['date']), function ($query) use($input) {
            $query->where('idx_month', intval($input['date']));
        })->count();
        if ($total) {
            $records = UserIntegral::query()->where('userid', $userid)->when(isset($input['date']) && intval($input['date']), function ($query) use($input) {
                $query->where('idx_month', intval($input['date']));
            })->offset($offset)->limit($pageSize)->select(['direction', 'typeid', 'amount', 'balance', 'remark', 'add_time'])->orderBy('id', 'desc')->get()->toArray();
            array_walk($records, function (&$v) {
                $v['directionName'] = AccountEnum::getDirection($v['direction']);
                $v['typeName'] = AccountEnum::getTypeName($v['typeid']);
                $v['createTime'] = date('Y-m-d H:i:s', $v['add_time']);
                unset($v['add_time']);
            });
        }
        return [
            'total' => $total,
            'pages' => ceil($total / $pageSize),
            'records' => $records ?? [],
        ];
    }

    /**
     * 获取优惠券.
     * @param mixed $userid
     * @param mixed $input
     */
    public static function getCouponList($userid, $input): array
    {
        $pageNum = max(1, intval($input['pageNum'] ?? 1));
        $pageSize = intval($input['pageSize'] ?? 20);
        if (isset($input['query']['status'])) {
            $where['status'] = (int)$input['query']['status'];
        }
        $where = ['userid' => $userid];
        $total = UserCoupon::query()->where($where)->count();
        if ($total) {
            $records = UserCoupon::query()->where($where)->offset(($pageNum - 1) * $pageSize)->limit($pageSize)->select(['distribute_time', 'use_time', 'expire_time', 'status', 'effective_time', 'amount'])->orderBy('id', 'desc')->get()->toArray();
            array_walk($records, function (&$v) {
                $v['effectiveDays'] = intval($v['effective_time'] / 86400);
                $v['expireTime'] = date('Y-m-d H:i:s', $v['expire_time']);
                $v['distributeTime'] = date('Y-m-d H:i:s', $v['distribute_time']);
                $v['useTime'] = $v['use_time'] ? date('Y-m-d H:i:s', $v['use_time']) : '-';
                unset($v['expire_time'], $v['distribute_time'], $v['effective_time'], $v['use_time']);
            });
        }
        return [
            'total' => $total,
            'pages' => ceil($total / $pageSize),
            'records' => $records ?? [],
        ];
    }

    /**
     * 获取优惠券详情.
     * @param mixed $couponId
     * @param mixed $userid
     * @throws Exception
     */
    public static function getCouponInfo($couponId, $userid = 0): array
    {
        $where = ['coupon_id' => $couponId];
        if ($userid) {
            $where['userid'] = $userid;
        }
        $coupon = UserCoupon::query()->where($where)->first();
        if (!$coupon) {
            throw new Exception('优惠券不存在');
        }
        return $coupon->toArray();
    }

    public static function useCoupon($couponId)
    {
    }

    /**
     * 修改昵称
     * @param $userid
     * @param $nickname
     * @return true
     * @throws Exception
     */
    public static function modifyUserNickName($userid, $nickname): true
    {
        $user = User::find($userid);
        if (!$user) {
            throw new Exception('用户不存在');
        }
        $user->nickname = $nickname;
        $user->save();
        return true;
    }

    /**
     * 格式化用户信息
     * @param $user
     * @param bool $isMyself
     * @return array
     */
    public static function formatUserInfo($user, $isMyself = false): array
    {
        $levelInfo = self::getUserLevelInfo((int)$user['integral']);
        $userInfo = [
            'userId' => $user['id'],
            'userName' => $user['username'],
            'nickName' => $user['nickname'],
            'avatar' => img_url($user['avatar']),
            'roleId' => $user['role_id'],
            'levelName' => $levelInfo['levelName'],
        ];
        if ($isMyself) {
            $userInfo['imId'] = $user['im_id'];
            $userInfo['mobile'] = maskMobile($user['mobile']);
            $userInfo['countryCode'] = $user['country_code'];
            $userInfo['email'] = maskEmail($user['email']);
            $userInfo['coins'] = $user['coins'];
            $userInfo['integral'] = $user['integral'];
            $userInfo['isRealName'] = $user['is_realname'];
            $userInfo['lastTime'] = $user['last_time'];
            $userInfo['lastIp'] = $user['last_ip'];
            $userInfo['upgradeIntegral'] = $levelInfo['upgradeIntegral'];
            $userInfo['coupons'] = 0;
        }
        return $userInfo;
    }

    /**
     * 获取关注主播
     * @param $userid
     * @param $input
     * @return array
     */
    public static function getMyFollowAnchorList($userid, $input): array
    {
        [, $pageSize, $offset] = parsePageParams($input);
        $total = FollowAnchor::query()->where('userid', $userid)->count();
        if ($total) {
            $res = FollowAnchor::with(['user', 'anchor'])->where('userid', $userid)->orderBy('id', 'desc')->offset($offset)->limit($pageSize)->get()->toArray();
            $records = [];
            foreach ($res as $r) {
                $records[] = [
                    'userId' => $r['user']['id'],
                    'userNick' => $r['user']['nickname'],
                    'userAvatar' => img_url($r['user']['avatar'], 48),
                    'roomId' => $r['anchor']['room_id'],
                    'liveStatus' => $r['anchor']['live_status'],
                    'fansCount' => $r['anchor']['fans_count'],
                    'followed' => 1,
                ];
            }
        }
        return [
            'total' => $total,
            'pages' => ceil($total / $pageSize),
            'records' => $records ?? []
        ];
    }

    /**
     * 操作账户金币/积分.
     * @param mixed $userid
     * @param mixed $direction
     * @param mixed $accountType
     * @param mixed $amount
     * @param mixed $typeid
     * @param mixed $extend
     */
    private static function _opAccount($userid, $direction, $accountType, $amount, $typeid, string $remark, array $extend): bool
    {
        try {
            Db::beginTransaction();
            $userinfo = User::find($userid);
            if (!$userinfo) {
                throw new Exception('账号不存在');
            }
            $field = $accountType == AccountEnum::COINS ? 'coins' : 'integral';
            if ($direction == AccountEnum::DIRECTION_OUT && $accountType == AccountEnum::COINS && isset($extend['couponId'])) {
                $coupon = self::getCouponInfo($extend['couponId'], $userid);
                if ($coupon['status'] != AccountEnum::COUPON_STATUS_USABLE || $coupon['expire_time'] && time() > $coupon['expire_time']) {
                    throw new Exception('该优惠券不可用');
                }
                $ret0 = UserCoupon::query()->where('id', $coupon['id'])->update([
                    'status' => AccountEnum::COUPON_STATUS_USED,
                    'use_time' => time(),
                ]);
                if (!$ret0) {
                    Db::rollBack();
                    throw new Exception('优惠券使用失败');
                }
                $extend['couponAmount'] = $coupon['amount'];
                $amount = max(0, $amount - $coupon['amount']);
            }
            if ($direction == AccountEnum::DIRECTION_OUT && ($accountType == AccountEnum::COINS && $userinfo['coins'] < $amount || $accountType == AccountEnum::INTEGRAL && $userinfo['integral'] < $amount)) {
                throw new Exception('账户余额不足');
            }
            // 使用优惠券后剩余支付金额为0
            if ($amount == 0) {
                Db::commit();
                return true;
            }
            $log = [
                'userid' => $userid,
                'direction' => $direction,
                'typeid' => $typeid,
                'amount' => $amount,
                'remark' => $remark,
                'extend' => new_json_encode($extend),
                'idx_month' => date('Ym'),
                'add_time' => time(),
            ];
            if ($direction == AccountEnum::DIRECTION_IN) {
                $ret1 = User::query()->where('id', $userid)->increment($field, $amount);
                $log['balance'] = ($accountType == AccountEnum::COINS ? $userinfo['coins'] : $userinfo['integral']) + $amount;
                if ($accountType == AccountEnum::COINS) {
                    $ret2 = UserCoin::create($log);
                } else {
                    $ret2 = UserIntegral::create($log);
                }
            } elseif ($direction == AccountEnum::DIRECTION_OUT) {
                $ret1 = User::query()->where('id', $userid)->decrement($field, $amount);
                $log['balance'] = ($accountType == AccountEnum::COINS ? $userinfo['coins'] : $userinfo['integral']) - $amount;
                if ($accountType == AccountEnum::COINS) {
                    $ret2 = UserCoin::create($log);
                } else {
                    $ret2 = UserIntegral::create($log);
                }
            } else {
                throw new Exception('未知操作类型');
            }
            if ($ret1 && $ret2) {
                Db::commit();
                return true;
            }
            Db::rollBack();
        } catch (Throwable $ex) {
            Db::rollBack();
        }
        return false;
    }

    /**
     * @throws Exception
     */
    private static function loginSuccess($user, $client, $loginType, $validId = 0): array
    {
        $token = self::createToken($user, $client);
        if (!$token) {
            throw new Exception('系统繁忙');
        }
        $ip = getClientIp();
        $lastTime = $user->last_time;
        $lastIp = $user->last_ip;
        $user->last_ip = $ip;
        $user->last_time = time();
        $user->save();

        self::createLog($user->userid, 'login', ['login_type' => $loginType]);
        if ($validId) {
            SmsLog::setValid($validId);
        }

        $userInfo = self::formatUserInfo($user, true);
        $userInfo['lastTime'] = $lastTime;
        $userInfo['lastIp'] = $lastIp;
        return [
            'token' => $token,
            'userInfo' => $userInfo,
            'unReadMsgNum' => 0
        ];
    }
}
